#define QT_NO_CAST_ASCII
#define QT_NO_ASCII_CAST

#include "get_opt.h"

#include <cstdlib>
#include <cassert>

GetOpt::GetOpt()
{
    if (!qApp)

        init(qApp->argc(), qApp->argv(), 1);
}

GetOpt::GetOpt(int offset)
{
    if (!qApp)

        init(qApp->argc(), qApp->argv(), offset);
}

GetOpt::GetOpt(int argc, char *argv[])
{
    init(argc, argv);
}

GetOpt::GetOpt(const QStringList &a)
        : args(a)
{
    init(0, 0);
}

void GetOpt::init(int argc, char *argv[], int offset)
{
    numReqArgs = numOptArgs = 0;
    currArg = 1; // appname is not part of the arguments
    if (argc) {
        // application name
        aname = QFileInfo(QString::fromUtf8(argv[0])).fileName();
        // arguments
        for (int i = offset; i < argc; ++i)
            args.append(QString::fromUtf8(argv[i]));
    }
}

bool GetOpt::parse(bool untilFirstSwitchOnly)
{
    //    qDebug( "parse(%s)", args.join( QString( "," ) ).ascii() );
    // push all arguments as we got them on a stack
    // more pushes might following when parsing condensed arguments
    // like --key=value.
    QStack<QString> stack;
    {
        QStringList::const_iterator it = args.empty() ? args.end() : --args.end();
        const QStringList::const_iterator end = args.end();
        while (it != end) {
            stack.push(*it);
            if (it == args.begin())
                break;
            --it;
        }
    }

    const OptionConstIterator obegin = options.begin();
    const OptionConstIterator oend = options.end();
    enum { StartState, ExpectingState, OptionalState } state = StartState;
    Option currOpt;
    enum TokenType { LongOpt, ShortOpt, Arg, End } t, currType = End;
    bool extraLoop = true; // we'll do an extra round. fake an End argument
    while (!stack.isEmpty() || extraLoop) {
        QString a;
        QString origA;
        // identify argument type
        if (!stack.isEmpty()) {
            a = stack.pop();
            currArg++;
            origA = a;
            //      qDebug( "popped %s", a.ascii() );
            if (a.startsWith(QString::fromLatin1("--"))) {
                // recognized long option
                a = a.mid(2);
                if (a.isEmpty()) {
                    exit(2);
                }
                t = LongOpt;
                // split key=value style arguments
                int equal = a.indexOf('=');
                if (equal >= 0) {
                    stack.push(a.mid(equal + 1));
                    currArg--;
                    a = a.left(equal);
                }
            } else if (a.length() == 1) {
                t = Arg;
            } else if (a[0] == '-') {
#if 0 // compat mode for -long style options
                if (a.length() == 2) {
                    t = ShortOpt;
                    a = a[1];
                } else {
                    a = a.mid(1);
                    t = LongOpt;
                    // split key=value style arguments
                    int equal = a.find('=');
                    if (equal >= 0) {
                        stack.push(a.mid(equal + 1));
                        currArg--;
                        a = a.left(equal);
                    }
                }
#else
                // short option
                t = ShortOpt;
                // followed by an argument ? push it for later processing.
                if (a.length() > 2) {
                    stack.push(a.mid(2));
                    currArg--;
                }
                a = a[1];
#endif
            } else {
                t = Arg;
            }
        } else {
            // faked closing argument
            t = End;
        }
        // look up among known list of options
        Option opt;
        if (t != End) {
            OptionConstIterator oit = obegin;
            while (oit != oend) {
                const Option &o = *oit;
                if ((t == LongOpt && a == o.lname) ||    // ### check state
                        (t == ShortOpt && a[0].unicode() == o.sname)) {
                    opt = o;
                    break;
                }
                ++oit;
            }
            if (t == LongOpt && opt.type == OUnknown) {
                if (currOpt.type != OVarLen) {
                    return false;
                } else {
                    // VarLength options support arguments starting with '-'
                    t = Arg;
                }
            } else if (t == ShortOpt && opt.type == OUnknown) {
                if (currOpt.type != OVarLen) {
                    return false;
                } else {
                    // VarLength options support arguments starting with '-'
                    t = Arg;
                }
            }

        } else {
            opt = Option(OEnd);
        }

        // interpret result
        switch (state) {
        case StartState:
            if (opt.type == OSwitch) {
                setSwitch(opt);
                setOptions.insert(opt.lname, 1);
                setOptions.insert(QString(QChar(opt.sname)), 1);
            } else if (opt.type == OArg1 || opt.type == ORepeat) {
                state = ExpectingState;
                currOpt = opt;
                currType = t;
                setOptions.insert(opt.lname, 1);
                setOptions.insert(QString(QChar(opt.sname)), 1);
            } else if (opt.type == OOpt || opt.type == OVarLen) {
                state = OptionalState;
                currOpt = opt;
                currType = t;
                setOptions.insert(opt.lname, 1);
                setOptions.insert(QString(QChar(opt.sname)), 1);
            } else if (opt.type == OEnd) {
                // we're done
            } else if (opt.type == OUnknown && t == Arg) {
                if (numReqArgs > 0) {
                    if (reqArg.stringValue->isNull()) {   // ###
                        *reqArg.stringValue = a;
                    } else {
                        return false;
                    }
                } else if (numOptArgs > 0) {
                    if (optArg.stringValue->isNull()) {   // ###
                        *optArg.stringValue = a;
                    } else {
                        return false;
                    }
                }
            } else {
            }
            break;
        case ExpectingState:
            if (t == Arg) {
                if (currOpt.type == OArg1) {
                    *currOpt.stringValue = a;
                    state = StartState;
                } else if (currOpt.type == ORepeat) {
                    currOpt.listValue->append(a);
                    state = StartState;
                } else {
                    abort();
                }
            } else {
                QString n = currType == LongOpt ?
                            currOpt.lname : QString(QChar(currOpt.sname));
                return false;
            }
            break;
        case OptionalState:
            if (t == Arg) {
                if (currOpt.type == OOpt) {
                    *currOpt.stringValue = a;
                    state = StartState;
                } else if (currOpt.type == OVarLen) {
                    currOpt.listValue->append(origA);
                    // remain in this state
                } else {
                    abort();
                }
            } else {
                // optional argument not specified
                if (currOpt.type == OOpt)
                    *currOpt.stringValue = currOpt.def;
                if (t != End) {
                    // re-evaluate current argument
                    stack.push(origA);
                    currArg--;
                }
                state = StartState;
            }
            break;
        }

        if (untilFirstSwitchOnly && opt.type == OSwitch)
            return true;

        // are we in the extra loop ? if so, flag the final end
        if (t == End)
            extraLoop = false;
    }

    if (numReqArgs > 0 && reqArg.stringValue->isNull()) {
        return false;
    }

    return true;
}

void GetOpt::addOption(Option o)
{
    // ### check for conflicts
    options.append(o);
}

void GetOpt::addSwitch(const QString &lname, bool *b)
{
    Option opt(OSwitch, 0, lname);
    opt.boolValue = b;
    addOption(opt);
    // ### could do all inits at the beginning of parse()
    *b = false;
}

void GetOpt::setSwitch(const Option &o)
{
    assert(o.type == OSwitch);
    *o.boolValue = true;
}

void GetOpt::addOption(char s, const QString &l, QString *v)
{
    Option opt(OArg1, s, l);
    opt.stringValue = v;
    addOption(opt);
    *v = QString::null;
}

void GetOpt::addVarLengthOption(const QString &l, QStringList *v)
{
    Option opt(OVarLen, 0, l);
    opt.listValue = v;
    addOption(opt);
    *v = QStringList();
}

void GetOpt::addRepeatableOption(char s, QStringList *v)
{
    Option opt(ORepeat, s, QString::null);
    opt.listValue = v;
    addOption(opt);
    *v = QStringList();
}

void GetOpt::addRepeatableOption(const QString &l, QStringList *v)
{
    Option opt(ORepeat, 0, l);
    opt.listValue = v;
    addOption(opt);
    *v = QStringList();
}

void GetOpt::addOptionalOption(const QString &l, QString *v,
                               const QString &def)
{
    addOptionalOption(0, l, v, def);
}

void GetOpt::addOptionalOption(char s, const QString &l,
                               QString *v, const QString &def)
{
    Option opt(OOpt, s, l);
    opt.stringValue = v;
    opt.def = def;
    addOption(opt);
    *v = QString::null;
}

void GetOpt::addArgument(const QString &name, QString *v)
{
    Option opt(OUnknown, 0, name);
    opt.stringValue = v;
    reqArg = opt;
    ++numReqArgs;
    *v = QString::null;
}

void GetOpt::addOptionalArgument(const QString &name, QString *v)
{
    Option opt(OUnknown, 0, name);
    opt.stringValue = v;
    optArg = opt;
    ++numOptArgs;
    *v = QString::null;
}

bool GetOpt::isSet(const QString &name) const
{
    return setOptions.find(name) != setOptions.end();
}

